
#include "Interpreter.h"
#include <iostream>
#include <iterator>
#include "Tokenizer.h"
#include <cctype>
#include <cassert>
#include "InfixToRPN.h"

Interpreter* Interpreter::mIP = NULL;

void lower(char& c)
{
	//
	// Ignore characters between start and end quotes "XxXx"
	//
	// This static flag is false before and after every query
	// coz every start " is followed by an end "
	//
	static bool toIgnore = false;
	if(c == '"')
	{
		if(toIgnore == false)
			toIgnore = true;
		else
			toIgnore = false;

		return;
	}

	if(toIgnore == false)
		c = tolower(c);
}

int Interpreter::Interpret(std::string& query)
{
	// convert to lower case
	std::for_each(query.begin(), query.end(), &lower);
	
	mTokens.clear();
	size_t queryPtr = 0;
	size_t pos;
	std::string tempStr;

	while(queryPtr != query.size())
	{
		if((pos = query.find_first_of('"', queryPtr)) == std::string::npos)
		{
			// tokenize entire substring till the end, and update string ptr
			tempStr = query.substr(queryPtr);
			Tokenizer(tempStr, mTokens);
			queryPtr = query.size();
		}
		else
		{
			// tokenize the substring till the " character, and update string ptr
			tempStr = query.substr(queryPtr, pos - queryPtr);
			Tokenizer(tempStr, mTokens);
			queryPtr = pos;
			
			// find the closing "
			pos = query.find_first_of('"', queryPtr+1);
			assert(pos != std::string::npos); // start " must have a matching end "

			mTokens.push_back(query.substr(queryPtr, pos - queryPtr + 1));

			queryPtr = pos + 1; // after the ending "
		}
	}

	/*std::cout << "Tokens are: " << std::endl;
	for(unsigned int i = 0; i < mTokens.size(); ++i)
		std::cout << mTokens[i] << std::endl;
	std::cout << std::endl;*/

	
	if(mTokens.empty())
		return -1;

	if(mTokens[0].compare("create") == 0)
	{
		std::cout << "CREATE" << std::endl;
		return HandleCreate();
	}
	else if(mTokens[0].compare("drop") == 0)
	{
		std::cout << "DROP" << std::endl;
		return HandleDrop();
	}
	else if(mTokens[0].compare("select") == 0)
	{
		std::cout << "SELECT" << std::endl;
		return HandleSelect();
	}
	else if(mTokens[0].compare("delete") == 0)
	{
		std::cout << "DELETE" << std::endl;
		return HandleDelete();
	}
	else if(mTokens[0].compare("insert") == 0)
	{
		std::cout << "INSERT" << std::endl;
		return HandleInsert();
	}
	else
	{
		std::cout << "Invalid query!" << std::endl;
		return -1;
	}
}

int Interpreter::HandleCreate()
{
	// create table table-name(attribute-name data-type, ...)

	assert(mTokens[1].compare("table") == 0);
	assert(mTokens[3].compare("(") == 0);
	
	std::vector<std::string> fieldNames;
	std::vector<enum FIELD_TYPE> fieldTypes;
	
	unsigned int i = 4;
	while(i < mTokens.size())
	{
		if(mTokens[i].compare(")") == 0)
			break;
		else if(mTokens[i].compare(",") == 0)
			++i;

		fieldNames.push_back(mTokens[i]); 
		++i;
		if(mTokens[i].compare("int") == 0)
			fieldTypes.push_back(INT);
		else
			fieldTypes.push_back(STR20);
		++i;
	}
	
	Schema schema(fieldNames, fieldTypes);
	std::cout << "Creating table: " << mTokens[2] << " with schema: \n" 
			<< schema << std::endl;

	if(mSchemaManager.createRelation(mTokens[2], schema) == NULL)
		return -1;
	
	std::cout << "Created table successfully!" << std::endl;

	return 0;
}

int Interpreter::HandleDrop()
{
	// drop table table-name

	assert(mTokens.size() == 3);
	assert(mTokens[1].compare("table") == 0);

	std::cout << "Dropping table: " << mTokens[2] << std::endl;
	
	if(mSchemaManager.deleteRelation(mTokens[2]) == false)
		return -1;

	return 0;
}

int Interpreter::HandleSelect()
{
	unsigned int fromIndex = mTokens.size(), whereIndex = mTokens.size(), 
		orderByIndex = mTokens.size();
	
	// check if duplicate elimination is required (DISTINCT after SELECT)
	bool distinct = false;
	if(mTokens[1].compare("distinct") == 0)
		distinct = true;

	unsigned int idx = 1;

	// start searching for FROM from index=1
	while(idx < mTokens.size())
	{
		if(mTokens[idx].compare("from") == 0)
		{
			fromIndex = idx;
			break;
		}
		++idx;
	}

	// start searching for WHERE from index = fromIndex + 1
	idx = fromIndex + 1;
	while(idx < mTokens.size())
	{
		if(mTokens[idx].compare("where") == 0)
		{
			whereIndex = idx;
			break;
		}
		++idx;
	}

	// start searching for ORDER BY from index = fromIndex + 1
	idx = fromIndex + 1;
	while(idx < mTokens.size())
	{
		if(mTokens[idx].compare("order") == 0)
		{
			if(mTokens[idx+1].compare("by") == 0)
			{
				orderByIndex = idx;
				break;
			}
		}
		++idx;
	}

	unsigned int fromEnd = std::min(whereIndex, orderByIndex), fromIter = fromIndex+1;
	std::vector<std::string> tables;
	while(fromIter != fromEnd)
	{
		if(mTokens[fromIter][0] == ',')
			++fromIter;
		
		tables.push_back(mTokens[fromIter]);
		++fromIter;
	}
	assert(tables.size() > 0);


	bool needJoin = false;
	if(tables.size() > 1)
		needJoin = true;


	// check if projection is not required (* before FROM)
	bool projection = true;
	if((mTokens[fromIndex - 1].compare("*") == 0) || (tables.size() > 2))
	{
		// projection not supported for join of more than 2 tables!

		projection = false;
	}


	// gather the attributes to project
	std::vector<std::string> projectionAttribs;
	if(projection == true)
	{
		// start from index = 1 if DISTINCT is not present, else index = 2
		if(distinct == true)
			idx = 2;
		else
			idx = 1;

		while(idx != fromIndex)
		{
			if(mTokens[idx][0] == ',')
				++idx;
			
			if(mTokens[idx][0] == '.')
			{
				// oops, the previous one was a table name
				std::string tblName = projectionAttribs.back();
				projectionAttribs.pop_back();

				++idx; // now pointing at the attrib
				std::string attribName = mTokens[idx];
				
				if(tables.size() == 1)
				{
					projectionAttribs.push_back(attribName);
				}
				else	// need to tweak the attrib to reflect new schema
				{
					// multiple tables
					if((attribName.compare(mTokens[whereIndex+3]) == 0) ||
						(attribName.compare(mTokens[whereIndex+7]) == 0))
					{
						projectionAttribs.push_back(attribName.append("01"));
					}
					else
					{
						// projecting some other attrib, find the real name
						unsigned int tbl = 0;
						for(; tbl < tables.size(); ++tbl)
						{
							if(tblName.compare(tables[tbl]) == 0)
								break;
						}
						assert(tbl != tables.size());

						char temp[10];
						sprintf(temp, "%s%d", attribName.c_str(), tbl);
						projectionAttribs.push_back(temp);
					}
				}
			}
			else
			{
				projectionAttribs.push_back(mTokens[idx]);
			}

			++idx;
		}

		std::cout << "The attribs to project upon are: " << std::endl;
		std::copy(projectionAttribs.begin(), projectionAttribs.end(),
			std::ostream_iterator<std::string, char>(std::cout, " "));
		std::cout << std::endl;
	}


	// gather ORDER BY attribute
	bool orderby = false;
	std::string orderbyAttrib;
	if((orderByIndex != mTokens.size()) && (tables.size() < 3))
	{
		// orderby not supported for join of more than 2 tables!

		orderby = true;
		orderbyAttrib.assign(mTokens.back());

		if(tables.size() == 2) // need to tweak the attrib to reflect new schema
		{
			//
			// join attrib is whereIndex+3 ... WHERE tbl1.attr1 = tbl2.attr2
			// or whereIndex+7
			if((orderbyAttrib.compare(mTokens[whereIndex + 3]) == 0) ||
				(orderbyAttrib.compare(mTokens[whereIndex + 7]) == 0))
			{
				// ordering by the join attrib
				orderbyAttrib.append("01");
			}
			else
			{
				// sorting by some other attrib, find the real name
				unsigned int tbl = 0;
				const std::string& tblName = mTokens[mTokens.size()-3];
				for(; tbl < tables.size(); ++tbl)
				{
					if(tblName.compare(tables[tbl]) == 0)
						break;
				}
				assert(tbl != tables.size());

				char temp[10];
				sprintf(temp, "%d", tbl);
				orderbyAttrib.append(temp);
			}
		}
	}


	// WHERE clause spans from whereIndex + 1 to orderByIndex (might be mTokens.size())
	std::vector<std::string> whereInfix;
	if(whereIndex != mTokens.size())
		whereInfix.assign(mTokens.begin() + whereIndex + 1, mTokens.begin() + orderByIndex);

	std::vector<Tuple> resultTuples;
	std::string resultName;
	bool selectionBeforeDisplay = false;

	std::vector<std::string> whereRPN;

	std::string deDupedSortedTableName;

	if(needJoin == false)
	{
		if(whereInfix.empty() == false) // there is some WHERE clause
		{
			if(shunting_yard(whereInfix, whereRPN) == true)
			{
				std::cout << "RPN notation of the WHERE clause: " << std::endl;
				for(unsigned int i = 0; i < whereRPN.size(); ++i)
				{
					std::cout << whereRPN[i] << " ";
				}
				std::cout << std::endl;
			}
			else
			{
				std::cout << "Conversion to RPN failed!" << std::endl;
				return -1;
			}
		}

		Relation* relationPtr = mSchemaManager.getRelation(tables.front());


		if((distinct == false) && (orderby == false))
		{
			// simple case, just read the tuples and filter and output
			// no join, no dedup, no sort
			
			selectionBeforeDisplay = false; // selection happens on read from disk
			int blocksToRead = relationPtr->getNumOfBlocks(), blockIndex = 0, blocksRead;
			std::vector<Tuple> tuples;
			while(blocksToRead != 0)
			{
				blocksRead = std::min(blocksToRead, mMem.getMemorySize());
				relationPtr->getBlocks(blockIndex, 0, blocksRead);
				tuples = mMem.getTuples(0, blocksRead);

				for(unsigned int tpl = 0; tpl < tuples.size(); ++tpl)
				{
					if(CheckWhereOnTuple(tuples[tpl], whereRPN))
						resultTuples.push_back(tuples[tpl]);
				}

				blockIndex += blocksRead;
				blocksToRead -= blocksRead;
			}
		}
		else
		{
			selectionBeforeDisplay = true;

			HandleDistinctAndOrderBy(tables.front(), distinct, orderby, orderbyAttrib,
				projection, projectionAttribs, resultTuples, deDupedSortedTableName);
		}
	}
	else
	{
		if(whereInfix.empty() == true)
		{
			selectionBeforeDisplay = false; // no selection for cross join cases
			int crossJoinRetVal = CrossJoin(tables, whereRPN, resultTuples, resultName);
		}
		else // natural join
		{
			// NaturalJoin() sets selectionBeforeDisplay and the updates whereRPN
			// if some selection is required at printing
			int naturalJoinRetVal;
			if(tables.size() == 2)
			{
				naturalJoinRetVal = NaturalJoin(tables, whereInfix, resultTuples, 
										resultName, whereRPN, selectionBeforeDisplay);
			}
			else if(tables.size() == 3)
			{
				naturalJoinRetVal = NaturalJoin3Tables(tables, whereInfix, resultTuples, 
										resultName, whereRPN, selectionBeforeDisplay);
			}
			else
			{
				std::cout << "Natural Join not implemented for more than 3 tables!" 
					<< std::endl;
				return -1;
			}
		}
		
		// now perform de-duping or sorting
		if((distinct == true) || (orderby == true))
		{
			//
			// we don't modify selectionBeforeDisplay here, it'll take care of itself
			// neither do we perform any projection here, thats left to just before printing
			//

			// need to write the resultTuples to resultName table
			AppendTuplesToRelation(resultName, resultTuples);
			resultTuples.clear();

			HandleDistinctAndOrderBy(resultName, distinct, orderby, orderbyAttrib,
				projection, projectionAttribs, resultTuples, deDupedSortedTableName);
		}
	}

	////////////////////////////////
	// The resultTuples at this stage is assumed to be generated without any available
	// main memory, i.e. ready to be outputted to the screen. Hence, only per-tuple operations
	// such as projection can be applied to the resultTuples hereon. For any aggregation
	// operations such as de-duping or sorting, the resultTuples should be written to disk
	// first. This is because there is no available main memory to hold all these tuples
	// at the same time.
	////////////////////////////////

	// apply projection, selection to these tuples before printing
	if(resultTuples.empty() == false)
	{
		if(projection == false)
		{
			for(unsigned int tpl = 0; tpl < resultTuples.size(); ++tpl)
			{
				// A -> B ===> ~A v B
				if((selectionBeforeDisplay == false) || 
					(CheckWhereOnTuple(resultTuples[tpl], whereRPN) == true))
				{
					std::cout << resultTuples[tpl] << std::endl;
				}
			}
		}
		else // projection by creating new schema, relation, tuple...
		{
			Schema resultSchema = resultTuples.front().getSchema();

			std::vector<enum FIELD_TYPE> projectionFieldTypes(projectionAttribs.size());
			for(unsigned int fld = 0; fld < projectionAttribs.size(); ++fld)
			{
				projectionFieldTypes[fld] = 
					resultSchema.getFieldType(projectionAttribs[fld]);
			}
			
			Schema projectedSchema(projectionAttribs, projectionFieldTypes);
			std::string projectedName(resultName);
			projectedName.append("_projected");
			Relation* projectedRelationPtr = 
				mSchemaManager.createRelation(projectedName, projectedSchema);

			Tuple projectedTuple = projectedRelationPtr->createTuple();

			for(unsigned int tpl = 0; tpl < resultTuples.size(); ++tpl)
			{
				// A -> B ===> ~A v B
				if((selectionBeforeDisplay == false) || 
					(CheckWhereOnTuple(resultTuples[tpl], whereRPN) == true))
				{
					//std::cout << resultTuples[tpl] << std::endl;
					
					for(unsigned int fld = 0; fld < projectionAttribs.size(); ++fld)
					{
						if(projectionFieldTypes[fld] == INT)
						{
							projectedTuple.setField(projectionAttribs[fld],
								resultTuples[tpl].getField(projectionAttribs[fld]).integer);
						}
						else
						{
							projectedTuple.setField(projectionAttribs[fld],
								*(resultTuples[tpl].getField(projectionAttribs[fld]).str));
						}
					}
					std::cout << projectedTuple << std::endl;
				}
			}
			
			mSchemaManager.deleteRelation(projectedName);
		}
	}

	if(deDupedSortedTableName.empty() == false)
		mSchemaManager.deleteRelation(deDupedSortedTableName);

	if(resultName.empty() == false)
		mSchemaManager.deleteRelation(resultName);

	return 0;
}

int Interpreter::HandleDelete()
{
	assert(mTokens[1].compare("from") == 0);
	
	Relation* relationPtr = mSchemaManager.getRelation(mTokens[2]);
	if(relationPtr == NULL)
		return -1;
	
	int retVal;

	if(mTokens.size() == 3) // DELETE FROM table-name
	{
		if(relationPtr->deleteBlocks(0) == false)
			retVal = -1;
		else
			retVal = 0;
	}
	else
	{
		// seems like DELETE can take all the burden of handling holes
		// DELETE has to take 2*B(R) time to perform, coz the entire table
		// is read and written. Why not pack all tuples into blocks
		// The packing happens in main memory, which is not what we care about
		// The eventual number of disk I/Os will be the same, the only overhead
		// will be the eventual deletion of those few blocks at the end of the
		// table which aren't needed because the table now fits in fewer blocks!
		// At the same time, fewer blocks are written after deletion due to packing

		// DELETE FROM table-name WHERE where-clause

		std::vector<std::string> whereInfix(mTokens.begin() + 4, mTokens.end());
		std::vector<std::string> whereRPN;
		if(shunting_yard(whereInfix, whereRPN) == true)
		{
			std::cout << "RPN notation of the WHERE clause: " << std::endl;
			for(unsigned int i = 0; i < whereRPN.size(); ++i)
			{
				std::cout << whereRPN[i] << " ";
			}
			std::cout << std::endl;
		}
		else
		{
			std::cout << "Conversion to RPN failed!" << std::endl;
			return -1;
		}
	
		//
		// Read 9 blocks of relation into memory 1-9, getTuples(),
		// iterate over vector and delete those tuples, setTuples()
		// into memory 1 onwards, write full blocks to disk, handle
		// the last non-full block by appending its tuples to memory 0,
		// write memory 0 to relation if full, and update memory 0 with
		// remainder
		//

		// initialize memory block 0
		mMem.getBlock(0)->clear();
		int blocksToRead = relationPtr->getNumOfBlocks();
		int blockReadIndex = 0, blockWriteIndex = 0, blocksRead = 0;
		std::vector<Tuple> inTuples, outTuples;

		while(blocksToRead != 0)
		{
			// memory block 0 contains remaining tuples from previous iteration
			if(mMem.getBlock(0)->isEmpty())
				outTuples.clear();
			else
				outTuples = mMem.getTuples(0, 1);

			// read more blocks from disk in memory block 1
			blocksRead = std::min(blocksToRead, mMem.getMemorySize()-1);
			relationPtr->getBlocks(blockReadIndex, 1, blocksRead);
			blockReadIndex += blocksRead;
			blocksToRead -= blocksRead;

			inTuples = mMem.getTuples(1, blocksRead); // this handles holes

			// filter out tuples satisfying the WHERE clause
			for(unsigned int tpl = 0; tpl < inTuples.size(); ++tpl)
			{
				if(CheckWhereOnTuple(inTuples[tpl], whereRPN) == false)
				{
					// this tuple doesn't satisfy the where clause
					// it doesn't need to be deleted, append to output
					outTuples.push_back(inTuples[tpl]);
				}
			}

			// write the final tuples to memory blocks 0 onwards
			mMem.setTuples(0, outTuples);

			// write those memory blocks to disk
			int tuples_per_block=outTuples.front().getTuplesPerBlock();
			int num_blocks=outTuples.size()/tuples_per_block;
			int num_additional_blocks=(outTuples.size()%tuples_per_block>0?1:0);

			relationPtr->setBlocks(blockWriteIndex, 0, num_blocks);
			blockWriteIndex += num_blocks;

			if(num_additional_blocks == 1)
			{
				// copy that extra memory block to block 0 for next iteration
				// 0 to num_blocks-1 are full blocks already written to disk
				// memory block 'num_blocks' has those tuples
				mMem.getBlock(0)->setTuples(
					mMem.getBlock(num_blocks)->getTuples());
			}
			else
			{
				// reset memory block 0
				mMem.getBlock(0)->clear();
			}
		}

		// now memory block 0 is either empty, or contains those extra tuples
		// for the last disk block
		if(mMem.getBlock(0)->isEmpty() == false)
		{
			relationPtr->setBlock(blockWriteIndex, 0);
			++blockWriteIndex;
		}

		// now clear out those blocks at the end of the relation which
		// got squeezed into 'blockWriteIndex' blocks
		std::cout << "Before deletion the relation had " << 
			relationPtr->getNumOfBlocks() << " blocks. After deletion, there are "
			<< blockWriteIndex << " blocks!" << std::endl;

		if(blockWriteIndex > relationPtr->getNumOfBlocks())
		{
			std::cout << "Delete error. Returning!" << std::endl;
			return -1;
		}
		
		relationPtr->deleteBlocks(blockWriteIndex);

		retVal = 0;
	}
	std::cout << "The relation now contains: " << std::endl;
	std::cout << *relationPtr << std::endl;

	return retVal;
}
int Interpreter::HandleInsert()
{
	// insert into table-name(attrib,...) values(vals,...)
	// insert into table-name(attrib,...) select * from table2-name

	//TODO: handle NULL, its 0 for some reason, see what is to be done

	assert(mTokens[1].compare("into") == 0);
	assert(mTokens[3].compare("(") == 0);
	
	Relation* relationPtr = mSchemaManager.getRelation(mTokens[2]);

	std::vector<std::string> fieldNames;
	
	int retVal;

	unsigned int i = 4; // after opening parentheses (
	while(i < mTokens.size())
	{
		if(mTokens[i].compare(")") == 0)
			break;
		else if(mTokens[i].compare(",") == 0)
			++i;

		fieldNames.push_back(mTokens[i]); 
		++i;
	}
	
	++i; // after closing parentheses )

	if(mTokens[i].compare("values") == 0)
	{
		++i; // at opening parentheses (
		assert(mTokens[i].compare("(") == 0);

		++i; // after opening parentheses (
		
		Tuple tuple = relationPtr->createTuple();
		unsigned int fieldIndex = 0; // index in fieldNames vector

		while(i < mTokens.size())
		{
			if(mTokens[i].compare(")") == 0)
				break;
			else if(mTokens[i].compare(",") == 0)
				++i;

			// atoi handles NULL by returning 0
			if(relationPtr->getSchema().getFieldType(fieldNames[fieldIndex]) == INT)
				tuple.setField(fieldNames[fieldIndex], atoi(mTokens[i].c_str()));
			else
			{
				if(mTokens[i].compare("null") == 0) // case lowered
				{
					tuple.setField(fieldNames[fieldIndex], std::string());
				}
				else
				{
					// ignore the starting and ending "
					tuple.setField(fieldNames[fieldIndex], 
						mTokens[i].substr(1, mTokens[i].length() - 2));
				}
			}

			++fieldIndex;
			++i;
		}
		assert(i == (mTokens.size() - 1));
		assert(fieldIndex == fieldNames.size());
	
		std::cout << "Tuple: " << tuple << std::endl;

		retVal = AppendTupleToRelation(relationPtr, 5, tuple);
	}
	else if(mTokens[i].compare("select") == 0)
	{
		// select * from table-name

		++i; // at attrib name
		
		assert(mTokens[i].compare("*") == 0);
		++i; // at FROM
		assert(mTokens[i].compare("from") == 0);
		++i; // at table-name
	
		// assume that the same table is being selected and inserted
		// else, the attrib order and number of attribs is not accounted for
		assert(mTokens[i].compare(mTokens[2]) == 0);

		//
		// bulk block handling:- till all blocks are done, read 10 blocks
		// at a time and append to relation.
		// The operation is performed faster than inserting tuples one by one
		// A hole gets created if the last block is also blindly copied
		// Solution: read the last block and save its tuples, delete the last block from
		// relation, bulk-copy the remaining blocks, now duplicate the saved tuples and
		// add at the end
		//

		relationPtr->getBlock(relationPtr->getNumOfBlocks()-1, 0); // read last block

		// determine if there is a hole to be handled
		bool needToHandleHoles = true;
		if(mMem.getBlock(0)->isFull())
			needToHandleHoles = false;

		if(needToHandleHoles == false)
		{
			// just duplicate all blocks, no special handling, saves a couple of I/Os
			const int numBlocks = relationPtr->getNumOfBlocks();
			int blockCtr = 0;
			while(blockCtr < numBlocks)
			{
				int blocksToCopy = std::min(numBlocks - blockCtr, mMem.getMemorySize());
				relationPtr->getBlocks(blockCtr, 0, blocksToCopy);
				relationPtr->setBlocks(relationPtr->getNumOfBlocks(), 0, blocksToCopy);
				blockCtr += blocksToCopy;
			}
		}
		else
		{
			// save tuples of the last block of relation that we just read into memory 0
			std::vector<Tuple> lastBlockTuples = mMem.getTuples(0, 1); 
			relationPtr->deleteBlocks(relationPtr->getNumOfBlocks()-1); // delete last block

			// now duplicate all blocks
			const int numBlocks = relationPtr->getNumOfBlocks();
			int blockCtr = 0;
			while(blockCtr < numBlocks)
			{
				int blocksToCopy = std::min(numBlocks - blockCtr, mMem.getMemorySize());
				relationPtr->getBlocks(blockCtr, 0, blocksToCopy);
				relationPtr->setBlocks(relationPtr->getNumOfBlocks(), 0, blocksToCopy);
				blockCtr += blocksToCopy;
			}

			// now double up the last block tuples and add to relation
			const unsigned int totalNumTuples = lastBlockTuples.size();
			for(unsigned int tpl = 0; tpl < totalNumTuples; ++tpl)
			{
				lastBlockTuples.push_back(lastBlockTuples[tpl]);
			}

			// write the doubled last tuples to memory blocks 0 onwards
			mMem.setTuples(0, lastBlockTuples);

			// write those memory blocks to disk
			int tuples_per_block=lastBlockTuples.front().getTuplesPerBlock();
			int num_blocks=lastBlockTuples.size()/tuples_per_block;
			int num_additional_blocks=(lastBlockTuples.size()%tuples_per_block>0?1:0);

			// the last one might not be full here FYI
			int blocksToCopy = num_blocks + num_additional_blocks; 
			relationPtr->setBlocks(relationPtr->getNumOfBlocks(), 0, blocksToCopy);
		}
		retVal = 0;
	}
	else
	{
		std::cout << "Invalid INSERT query" << std::endl;
		return -1;
	}

	std::cout << "The relation now contains: " << std::endl;
	std::cout << *relationPtr << std::endl;

	return retVal;
}


int Interpreter::AppendTupleToRelation
	(Relation* relation_ptr, int memory_block_index, Tuple& tuple)
{
	Block* block_ptr;
	if (relation_ptr->getNumOfBlocks()==0) 
	{
		cout << "The relation is empty" << endl;
		cout << "Get the handle to the memory block " 
			<< memory_block_index << " and clear it" << endl;
		block_ptr=mMem.getBlock(memory_block_index);
		block_ptr->clear(); //clear the block
		block_ptr->appendTuple(tuple); // append the tuple
		cout << "Write to the first block of the relation" << endl;
		if(relation_ptr->setBlock
			(relation_ptr->getNumOfBlocks(),memory_block_index) == false)
			return -1;
		else
			return 0;
	} 
	else 
	{
		if(tuple.getTuplesPerBlock() == 1)
		{
			// the last block can't be written on, anyway
			Block* blockPtr = mMem.getBlock(memory_block_index);
			blockPtr->clear();
			blockPtr->appendTuple(tuple);
			//write back to the relation
			if(relation_ptr->setBlock
				(relation_ptr->getNumOfBlocks(),memory_block_index)	== false)
				return -1;
			else
				return 0;
		}

		cout << "Read the last block of the relation into memory block " 
			<< memory_block_index << endl;
		relation_ptr->getBlock(relation_ptr->getNumOfBlocks()-1,memory_block_index);
		block_ptr=mMem.getBlock(memory_block_index);

		if (block_ptr->isFull()) 
		{
			cout << "(The block is full: Clear the memory block and append the tuple)" << endl;
			block_ptr->clear(); //clear the block
			block_ptr->appendTuple(tuple); // append the tuple
			cout << "Write to a new block at the end of the relation" << endl;
			//write back to the relation
			if(relation_ptr->setBlock
				(relation_ptr->getNumOfBlocks(),memory_block_index)	== false)
				return -1;
			else
				return 0;
		} 
		else 
		{
			cout << "(The block is not full: Append it directly)" << endl;
			block_ptr->appendTuple(tuple); // append the tuple
			cout << "Write to the last block of the relation" << endl;
			//write back to the relation
			if(relation_ptr->setBlock
				(relation_ptr->getNumOfBlocks()-1,memory_block_index) == false)
				return -1;
			else
				return 0;
		}
	}  
}

bool Interpreter::CheckWhereOnTuple(const Tuple& tuple, 
					const std::vector<std::string>& whereRPN) const
{

	if(whereRPN.empty())
		return true;

	std::stack<std::string> runStack;
	
	Schema tupleSchema = tuple.getSchema();

	unsigned int wherePtr = 0, whereEnd = whereRPN.size();
	while(wherePtr != whereEnd)
	{
		if(is_operator(whereRPN[wherePtr]))
		{
			const std::string& op = whereRPN[wherePtr];

			if(op.compare("not") == 0) // the only single operand op
			{
				std::string operand = runStack.top();
				runStack.pop();

				if(operand.compare("true") == 0)
					runStack.push("false");
				else if(operand.compare("false") == 0)
					runStack.push("true");
				else
				{
					assert(false);
				}
			}
			else
			{
				// two operand operators
				std::string operand2 = runStack.top();
				runStack.pop();
				std::string operand1 = runStack.top();
				runStack.pop();

				if(op.compare(".") == 0)
				{
					assert(false);
				}
				else if((op.compare("and") == 0) || (op.compare("or") == 0))
				{
					// the top two of stack must be boolean

					bool operand1Val, operand2Val, resultVal;
					if(operand1.compare("true") == 0)
						operand1Val = true;
					else if(operand1.compare("false") == 0)
						operand1Val = false;
					else
						assert(false);

					if(operand2.compare("true") == 0)
						operand2Val = true;
					else if(operand2.compare("false") == 0)
						operand2Val = false;
					else
						assert(false);
	
					if(op.compare("and") == 0)
						resultVal = operand1Val && operand2Val;
					else
						resultVal = operand1Val || operand2Val;

					if(resultVal)
						runStack.push("true");
					else
						runStack.push("false");
				}
				else
				{
					// = > < + - * /
					// the operands can be table attribName, INT, STR20
					// the result can be INT or boolean

					bool stringArgs = false, intArgs = false;
					std::string operand1ValStr, operand2ValStr;
					int operand1ValInt = 0, operand2ValInt = 0;

					// whats operator1 like?
					if(operand1[0] == '"')
					{
						stringArgs = true;
						operand1ValStr = operand1.substr(1, operand1.length() - 2);
					}
					else if((operand1[0] >= '0') && (operand1[0] <= '9'))
					{
						intArgs = true;
						operand1ValInt = atoi(operand1.c_str());
					}
					else
					{
						// operand1 is some attrib name
						if(tupleSchema.getFieldType(operand1) == INT)
						{
							intArgs = true;
							operand1ValInt = tuple.getField(operand1).integer;
						}
						else
						{
							stringArgs = true;
							operand1ValStr = *(tuple.getField(operand1).str);
						}
					}
					assert(stringArgs != intArgs); // either one is set

					// whats operator2 like?
					if(operand2[0] == '"')
					{
						stringArgs = true;
						operand2ValStr = operand2.substr(1, operand2.length() - 2);
					}
					else if((operand2[0] >= '0') && (operand2[0] <= '9'))
					{
						intArgs = true;
						operand2ValInt = atoi(operand2.c_str());
					}
					else
					{
						// operand2 is some attrib name
						if(tupleSchema.getFieldType(operand2) == INT)
						{
							intArgs = true;
							operand2ValInt = tuple.getField(operand2).integer;
						}
						else
						{
							stringArgs = true;
							operand2ValStr = *(tuple.getField(operand2).str);
						}
					}
					assert(stringArgs != intArgs); // either one is set
			
					assert(!stringArgs || (op.compare("=") == 0)); // stringArgs -> equality

					if(stringArgs)
					{
						if(operand1ValStr.compare(operand2ValStr) == 0)
							runStack.push("true");
						else
							runStack.push("false");
					}
					else
					{
						int resultInt;
						bool pleasePushIntResult = false;

						switch(op[0])
						{
							case '=':
								if(operand1ValInt == operand2ValInt)
									runStack.push("true");
								else
									runStack.push("false");
							break;

							case '>':
								if(operand1ValInt > operand2ValInt)
									runStack.push("true");
								else
									runStack.push("false");
							break;

							case '<':
								if(operand1ValInt < operand2ValInt)
									runStack.push("true");
								else
									runStack.push("false");
							break;

							case '+':
								pleasePushIntResult = true;
								resultInt = operand1ValInt + operand2ValInt;
							break;

							case '-':
								pleasePushIntResult = true;
								resultInt = operand1ValInt - operand2ValInt;
							break;

							case '*':
								pleasePushIntResult = true;
								resultInt = operand1ValInt * operand2ValInt;
							break;

							case '/':
								pleasePushIntResult = true;
								resultInt = operand1ValInt / operand2ValInt;
							break;
						}

						if(pleasePushIntResult)
						{
							char resultStr[20];
							sprintf(resultStr, "%d", resultInt);
							runStack.push(resultStr);
						}
					}
				}
			}
		}
		else
		{
			runStack.push(whereRPN[wherePtr]);
		}

		++wherePtr;
	}

	assert(runStack.size() == 1);
	if(runStack.top().compare("true") == 0)
		return true;
	else if(runStack.top().compare("false") == 0)
		return false;
	else
		assert(false);
}



std::string Interpreter::GetJoinedName(std::vector<std::string>& tableNames) const
{
	std::string result(tableNames[0]);
	for(unsigned int i = 1; i < tableNames.size(); ++i)
	{
		result += "_";
		result += tableNames[i];
	}
	return result;
}



int Interpreter::AppendTuplesToRelation(const std::string& tableName, 
										const std::vector<Tuple>& tuples)
{
	if(mSchemaManager.relationExists(tableName) == false)
		return -1;
	
	if(tuples.empty() == true)
		return 0;

	unsigned int tplIdx = 0, tplCount = tuples.size(),
		tuplesPerBlock = tuples.front().getTuplesPerBlock();
	Relation* relationPtr = mSchemaManager.getRelation(tableName);

	std::vector<Tuple> tempTuples;
	int tuplesToCopy, blocksToCopy;

	while(tplIdx < tplCount)
	{
		if((tplCount - tplIdx)/tuplesPerBlock > mMem.getMemorySize()) // max 10 blocks
		{
			tuplesToCopy = mMem.getMemorySize()*tuplesPerBlock;
			blocksToCopy = mMem.getMemorySize();
		}
		else if(tplCount - tplIdx < tuplesPerBlock) // last block
		{
			tuplesToCopy = tplCount - tplIdx;
			blocksToCopy = 1;
		}
		else // k full blocks
		{
			blocksToCopy = (tplCount - tplIdx) / tuplesPerBlock;
			tuplesToCopy = blocksToCopy * tuplesPerBlock;
		}
		
		tempTuples.assign(tuples.begin() + tplIdx,
			tuples.begin() + tplIdx + tuplesToCopy);

		mMem.setTuples(0, tempTuples);
		relationPtr->setBlocks(relationPtr->getNumOfBlocks(), 0, blocksToCopy);
		
		tplIdx += tuplesToCopy;
	}

	return 0;
}






